#ifndef __fluxio_proto_hpp__
#define __fluxio_proto_hpp__

#pragma once

#include <set>
#include <map>
#include <queue>

#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/program_options.hpp>

#include <boost/guid.hpp>

#include <rapidxml.hpp>
#include <rapidxml_print.hpp>

#include <cm/client_server_01/connections_mngr.hpp>
#include <cm/client_server_01/http_proto.hpp>

namespace fluxio
{
//----------------------------------------------------------------------

class vars_manager : public cm::client_server::http::manager
{
public:
	vars_manager()
	{
		// Todo: Setting of "advice_manager_ptr" can happen after this constructor so we need another place where add channel to it
		if (cm::client_server::http::advice_manager_ptr != 0)
		{
			cm::client_server::http::advice_manager_ptr->add_channel("vars");
		}
	}
	virtual ~vars_manager()
	{
	}
private:
	virtual void get_handle(cm::client_server::connection_handlers& conn_handlers,
		const cm::client_server::http::message& message,
			boost::function<void(bool)> handle_deliver_end)
	{
		cm::client_server::http::reply rep;

		if (message.splitted_uri_path_parts_.size() < 3)
		{
			rep = cm::client_server::http::reply::stock_reply(cm::client_server::http::reply::bad_request);
			conn_handlers.handle_deliver(rep.to_streambuf_ptr(),handle_deliver_end);
			return;
		}

		std::string manager_name = message.splitted_uri_path_parts_[2];
		if (managers_.find(boost::algorithm::to_lower_copy(manager_name)) != managers_.end())
		{
			managers_[manager_name]->get_handle(conn_handlers,message,handle_deliver_end);
			return;
		}

		rep = cm::client_server::http::reply::stock_reply(cm::client_server::http::reply::bad_request);
		conn_handlers.handle_deliver(rep.to_streambuf_ptr(),handle_deliver_end);
	}
	virtual void post_handle(cm::client_server::connection_handlers& conn_handlers,
		const cm::client_server::http::message& message,
			boost::function<void(bool)> handle_deliver_end)
	{
		cm::client_server::http::reply rep = cm::client_server::http::reply::stock_reply(cm::client_server::http::reply::not_implemented);
		conn_handlers.handle_deliver(rep.to_streambuf_ptr(),handle_deliver_end);
	}
public:
	void OnChangeVarValue(/*...*/)
	{
		static unsigned long dummy_counter = 0;

		cm::client_server::http::advice_manager::xml_node_ptr ptr_doc(new rapidxml::xml_document<>());
		
		// Feed
		rapidxml::xml_node<>* feed_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("feed"));
		rapidxml::xml_attribute<>* attr1 = ptr_doc->allocate_attribute(ptr_doc->allocate_string("xmlns"),ptr_doc->allocate_string("http://www.w3.org/2005/Atom"));
		feed_node->append_attribute(attr1);
		rapidxml::xml_attribute<>* attr2 = ptr_doc->allocate_attribute(ptr_doc->allocate_string("xmlns:fluxio"),ptr_doc->allocate_string("http://fluxio.blogspot.com"));
		feed_node->append_attribute(attr2);

		// Title
		rapidxml::xml_node<>* title_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("title"),ptr_doc->allocate_string("Vars notification"));
		feed_node->append_node(title_node);

		// Id
		rapidxml::xml_node<>* id_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("id"),ptr_doc->allocate_string("vars"));
		feed_node->append_node(id_node);

		// category
		rapidxml::xml_node<>* category_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("category"),"");
		std::string category = "vars";
		rapidxml::xml_attribute<>* attrterm = ptr_doc->allocate_attribute(ptr_doc->allocate_string("term"),ptr_doc->allocate_string(category.c_str()));
		category_node->append_attribute(attrterm);
		feed_node->append_node(category_node);

		// Link
/*
		rapidxml::xml_node<>* link_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("link"),ptr_doc->allocate_string(""));
		rapidxml::xml_attribute<>* attrhref = ptr_doc->allocate_attribute(ptr_doc->allocate_string("href"),ptr_doc->allocate_string("/vars/values"));
		link_node->append_attribute(attrhref);
		rapidxml::xml_attribute<>* attrrel = ptr_doc->allocate_attribute(ptr_doc->allocate_string("rel"),ptr_doc->allocate_string("self"));
		link_node->append_attribute(attrrel);
		feed_node->append_node(link_node);
*/
		// Updated
		boost::posix_time::ptime now = boost::posix_time::second_clock::universal_time();
		std::string updated = boost::posix_time::to_iso_extended_string(now) + "Z";
		rapidxml::xml_node<>* updated_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("updated"),ptr_doc->allocate_string(updated.c_str()));
		feed_node->append_node(updated_node);

		// Entries
		rapidxml::xml_node<>* entry_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("entry"),ptr_doc->allocate_string(""));
		{
			// Title
			rapidxml::xml_node<>* title_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("title"),ptr_doc->allocate_string("VarName"));
			entry_node->append_node(title_node);

			// Id
			rapidxml::xml_node<>* id_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("id"),ptr_doc->allocate_string("VarId"));
			entry_node->append_node(id_node);

			// category
			rapidxml::xml_node<>* category_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("category"),"");
			std::string category = "values";
			rapidxml::xml_attribute<>* attrterm = ptr_doc->allocate_attribute(ptr_doc->allocate_string("term"),ptr_doc->allocate_string(category.c_str()));
			category_node->append_attribute(attrterm);
			entry_node->append_node(category_node);

			// Link
			rapidxml::xml_node<>* link_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("link"),ptr_doc->allocate_string(""));
			rapidxml::xml_attribute<>* attrhref = ptr_doc->allocate_attribute(ptr_doc->allocate_string("href"),ptr_doc->allocate_string("/vars/values/VarId"));
			link_node->append_attribute(attrhref);
			rapidxml::xml_attribute<>* attrrel = ptr_doc->allocate_attribute(ptr_doc->allocate_string("rel"),ptr_doc->allocate_string("self"));
			link_node->append_attribute(attrrel);
			entry_node->append_node(link_node);

			// Updated - Now is the system time in the future will be acquisition time
			boost::posix_time::ptime now = boost::posix_time::second_clock::universal_time();
			std::string updated = boost::posix_time::to_iso_extended_string(now) + "Z";
			rapidxml::xml_node<>* updated_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("updated"),ptr_doc->allocate_string(updated.c_str()));
			entry_node->append_node(updated_node);

			// Value

			std::string dummy = boost::lexical_cast<std::string>(dummy_counter++);

			rapidxml::xml_node<>* value_node = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("fluxio:value"),ptr_doc->allocate_string(dummy.c_str()));
			std::string type = "Fluxio types = 'string', 'number' or 'datetime'";
			rapidxml::xml_attribute<>* attrtype = ptr_doc->allocate_attribute(ptr_doc->allocate_string("type"),ptr_doc->allocate_string(type.c_str()));
			value_node->append_attribute(attrtype);
			entry_node->append_node(value_node);

			// Status
			rapidxml::xml_node<>* value_status = ptr_doc->allocate_node(rapidxml::node_element,ptr_doc->allocate_string("fluxio:status"),"VarStatus");
			entry_node->append_node(value_status);
		}
		feed_node->append_node(entry_node);

		ptr_doc->append_node(feed_node);

		cm::client_server::http::advice_manager_ptr->deliver("vars",ptr_doc);
	}
private:
	std::map<std::string,cm::client_server::http::manager::manager_ptr>	managers_;
};

//----------------------------------------------------------------------

static boost::shared_ptr<vars_manager> var_manager_ptr;

//----------------------------------------------------------------------

class get_responser
{
public:
	get_responser(const cm::client_server::http::message& hh,
			boost::program_options::variables_map& vm,
			cm::client_server::connection_handlers& connection_handlers) :
			vm_(vm),message_(hh),
				connection_handlers_(connection_handlers),default_get_responser_(hh,vm,connection_handlers)
	{
		managers_["vars"] = var_manager_ptr;
	}
	~get_responser()
	{
	}
public:
	void handle()
	{
		if (message_.splitted_uri_path_parts_.size() < 2)
		{
			cm::client_server::http::reply rep;
			rep = cm::client_server::http::reply::stock_reply(cm::client_server::http::reply::bad_request);
			connection_handlers_.handle_deliver(rep.to_streambuf_ptr(),boost::bind(&get_responser::handle_deliver_end, this,_1));
			return;
		}

		std::string manager_name = message_.splitted_uri_path_parts_[1];
		if (managers_.find(boost::algorithm::to_lower_copy(manager_name)) != managers_.end())
		{
			managers_[manager_name]->get_handle(connection_handlers_,message_,boost::bind(&get_responser::handle_deliver_end,this,_1));
			return;
		}

		// default Behavior
		default_get_responser_.handle();
	}
private:
	void handle_deliver_end(bool done)
	{
		if (done)
		{
/*
#if defined(HTTP_KEEP_ALIVE)
			connection_handlers_.handle_done();
#endif
#if !defined(HTTP_KEEP_ALIVE)
			connection_handlers_.handle_close();
#endif
*/
			connection_handlers_.handle_close();
		}
	}
private:
	const cm::client_server::http::message&								message_;
	boost::program_options::variables_map&								vm_;
	cm::client_server::connection_handlers&								connection_handlers_;
private:
	std::map<std::string,cm::client_server::http::manager::manager_ptr>	managers_;
private:
	cm::client_server::http::get_responser								default_get_responser_;
};

//----------------------------------------------------------------------

class post_responser : public cm::client_server::http::post_responser
{
public:
	post_responser(const cm::client_server::http::message& hh,
		boost::program_options::variables_map& vm,
		cm::client_server::connection_handlers& connection_handlers) :
			cm::client_server::http::post_responser(hh,vm,connection_handlers)
		{
			managers_["vars"] = var_manager_ptr;
		}
};

//----------------------------------------------------------------------

typedef cm::client_server::http::get_handler<fluxio::get_responser>				get_handler;
typedef cm::client_server::http::post_handler<fluxio::post_responser>			post_handler;
// typedef cm::client_server::http::put_handler<fluxio::put_responser>			put_handler;
typedef cm::client_server::http::verbs_reader<get_handler,post_handler/*,...*/>	verbs_reader;

}

#endif //__fluxio_proto_hpp__
